//
// Created by Criinal Nicole on 2021/5/11.
//

#ifndef GFD_GFD_H
#define GFD_GFD_H


#include "Graph.h"
#include "../Discovering/VF2.h"

enum LiteralType {
    DEFAULT_LITERAL,
    CONSTANT_LITERAL,
    VARIABLE_LITERAL
};


class ConstantLiteral {
public:
    bool _active{false};
    int _index{}, _propertyName{};
    PropertyValue _constPropertyValue{};

    ConstantLiteral() = default;

    ConstantLiteral(int index, int propertyName,
                    PropertyValue constPropertyValue) :
            _index(index),
            _propertyName(propertyName),
            _constPropertyValue(constPropertyValue) {
    }

    ConstantLiteral(ConstantLiteral &constantLiteral) = default;

    bool matchCheck(Graph &queryGraph,
                    Graph &dataGraph,
                    std::map<int, int> &matchState) {
        auto &dataVertex = dataGraph.at(matchState[_index]);
//        std::cout <<"id: "<< dataVertex.getId() << std::endl;
        for (auto &property:dataVertex.PropertySet) {
            if (property._propertyName == _propertyName) {
                return property._propertyValue == _constPropertyValue;
            }
        }
        return false;
    };

    bool operator==(const ConstantLiteral &right) const {
        if (_index != right._index)return false;
        if (_propertyName != right._propertyName)return false;
        return (_constPropertyValue == right._constPropertyValue);
    }

    void fileOut(std::ofstream &fOut) {
        fOut << "constantLiteral "
             << _index << " "
             << _propertyName << " "
             << _constPropertyValue << std::endl;
    }

    [[nodiscard]] bool conflict(const ConstantLiteral &rightCon) const {
        return (_index == rightCon._index) && (_propertyName == rightCon._propertyName);
    }

    friend std::ostream &operator<<(std::ostream &out, ConstantLiteral &constantLiteral) {
        out << constantLiteral._index << "." << constantLiteral._propertyName
            << "="
            << constantLiteral._constPropertyValue;
        return out;
    }

};

class VariableLiteral {
public:
    bool _active{false};
    int _xIndex{}, _yIndex{};
    int _xPropertyName{}, _yPropertyName{};

    VariableLiteral() = default;

    VariableLiteral(int xIndex,
                    int xPropertyName,
                    int yIndex,
                    int yPropertyName) :
            _xIndex(xIndex),
            _xPropertyName(xPropertyName),
            _yIndex(yIndex),
            _yPropertyName(yPropertyName) {
    }

    bool operator==(const VariableLiteral &right) const {
        if (_xIndex != right._xIndex)return false;
        if (_xPropertyName != right._xPropertyName)return false;
        if (_yIndex != right._yIndex)return false;
        return _yPropertyName == right._yPropertyName;
    }

    void fileOut(std::ofstream &fOut) {
        fOut << "variableLiteral "
             << _xIndex << " "
             << _xPropertyName << " "
             << _yIndex << " "
             << _yPropertyName << std::endl;
    }

    bool matchCheck(Graph &queryGraph,
                    Graph &dataGraph,
                    std::map<int, int> &matchState) {
        auto &xDataVertex = dataGraph.at(matchState[_xIndex]);
        auto &yDataVertex = dataGraph.at(matchState[_yIndex]);
        for (auto &xProperty:xDataVertex.PropertySet) {
            if (xProperty._propertyName == _xPropertyName) {
                for (auto &yProperty:yDataVertex.PropertySet) {
                    if (yProperty._propertyName == _yPropertyName) {
                        return xProperty._propertyValue == yProperty._propertyValue;
                    }
                }
                return false;
            }
        }
        return false;
    }

    friend std::ostream &operator<<(std::ostream &out, VariableLiteral &variableLiteral) {
        out << variableLiteral._xIndex << "." << variableLiteral._xPropertyName
            << "="
            << variableLiteral._yIndex << "." << variableLiteral._yPropertyName;
        return out;
    }
};

class Literal {
public:

    LiteralType _literalType{DEFAULT_LITERAL};

    bool matchCheck(Graph &queryGraph,
                    Graph &dataGraph,
                    std::map<int, int> &matchState) {
        if (_literalType == CONSTANT_LITERAL) {
            return _constantLiteral.matchCheck(queryGraph, dataGraph, matchState);
        } else return _variableLiteral.matchCheck(queryGraph, dataGraph, matchState);
    };

    bool operator==(const Literal &right) const {
        if (_literalType != right._literalType)return false;
        if (_literalType == CONSTANT_LITERAL) {
            return _constantLiteral == right._constantLiteral;
        }
        if (_literalType == VARIABLE_LITERAL) {
            return _variableLiteral == right._variableLiteral;
        }
        return true;
    }

    ConstantLiteral _constantLiteral{};
    VariableLiteral _variableLiteral{};

    Literal() = default;

    Literal(const Literal &literal) {
        _literalType = literal._literalType;
        if (_literalType == CONSTANT_LITERAL) {
            _constantLiteral = literal._constantLiteral;
        }
        if (_literalType == VARIABLE_LITERAL) {
            _variableLiteral = literal._variableLiteral;
        }
    }

    friend std::ostream &operator<<(std::ostream &out, Literal &literal) {
        if (literal._literalType == DEFAULT_LITERAL) {
            out << "DEFAULT_LITERAL";
        } else if (literal._literalType == CONSTANT_LITERAL) {
            out << literal._constantLiteral;
        } else {
            out << literal._variableLiteral;
        }
        return out;
    }
};


typedef std::pair<
        std::vector<Literal>,
        std::vector<Literal>
>
        LiteralPair;

class GFD {
public:
    Graph _pattern{};

    std::vector<LiteralPair> _literalPairVector{};

    static int _maxXLiteralVectorSize;
    static int _maxYLiteralVectorSize;
    static int _maxLiteralSize;

    static int _maxVertexSize;
    static int _maxEdgeSize;

    GFD() = default;

    GFD(const GFD &gfd) = default;

    GFD(Graph &pattern, std::vector<LiteralPair> &literalPairVector)
            : _pattern(pattern),
              _literalPairVector(literalPairVector) {}

    friend std::istream &operator>>(std::istream &ifstream, GFD &gfd) {

        gfd._pattern.clear();
        int vertexNumber;
        ifstream >> vertexNumber;
        if (ifstream.eof())
            return ifstream;
        for (int i = 0; i < vertexNumber; ++i) {
            int id, label;
            ifstream >> id >> label;
            Vertex tmpVertex(id, label);
            int propertyNumber;
            ifstream >> propertyNumber;
            for (int j = 0; j < propertyNumber; ++j) {
                int propertyName;
                auto *propertyValue = new int;
                ifstream >> propertyName >> (*propertyValue);
                PropertyValue _propertyValue;
                _propertyValue._propertyValueType = INT;
                _propertyValue._propertyValuePointer = propertyValue;
                tmpVertex.PropertySet.insert({propertyName, _propertyValue});
            }
            gfd._pattern.push_back(tmpVertex);
        }

        int edgeNumber;
        ifstream >> edgeNumber;
        for (int i = 0; i < edgeNumber; ++i) {
            int from, to, label;
            ifstream >> from >> to >> label;
            Graph::makeEdge(gfd._pattern.at(from), gfd._pattern.at(to), label);
        }

        int literalSize;
        ifstream >> literalSize;
        for (int i = 0; i < literalSize; ++i) {
            LiteralPair literalPair;
            int xSize;
            ifstream >> xSize;
            for (int j = 0; j < xSize; ++j) {
                std::string literalType;
                ifstream >> literalType;
                if (literalType.at(0) == 'c') {
                    int ind, propertyName;
                    auto valuePointer = new int;
                    ifstream >> ind >> propertyName >> (*valuePointer);

                    PropertyValue propertyValue;
                    propertyValue._propertyValueType = INT;
                    propertyValue._propertyValuePointer = valuePointer;
                    ConstantLiteral constantLiteral(ind, propertyName, propertyValue);

                    Literal literal;
                    literal._constantLiteral = constantLiteral;
                    literal._literalType = CONSTANT_LITERAL;
                    literalPair.first.push_back(literal);
                } else {
                    int xInd, yInd, xPropertyName, yPropertyName;
                    ifstream >> xInd >> xPropertyName >> yInd >> yPropertyName;
                    VariableLiteral variableLiteral(xInd, xPropertyName, yInd, yPropertyName);

                    Literal literal;
                    literal._variableLiteral = variableLiteral;
                    literal._literalType = VARIABLE_LITERAL;
                    literalPair.first.push_back(literal);
                }
            }
            int ySize;
            ifstream >> ySize;
            for (int j = 0; j < ySize; ++j) {
                std::string literalType;
                ifstream >> literalType;
                if (literalType.at(0) == 'c') {
                    int ind, propertyName;
                    auto valuePointer = new int;
                    ifstream >> ind >> propertyName >> (*valuePointer);

                    PropertyValue propertyValue;
                    propertyValue._propertyValueType = INT;
                    propertyValue._propertyValuePointer = valuePointer;
                    ConstantLiteral constantLiteral(ind, propertyName, propertyValue);

                    Literal literal;
                    literal._constantLiteral = constantLiteral;
                    literal._literalType = CONSTANT_LITERAL;
                    literalPair.second.push_back(literal);
                } else {
                    int xInd, yInd, xPropertyName, yPropertyName;
                    ifstream >> xInd >> xPropertyName >> yInd >> yPropertyName;
                    VariableLiteral variableLiteral(xInd, xPropertyName, yInd, yPropertyName);

                    Literal literal;
                    literal._variableLiteral = variableLiteral;
                    literal._literalType = VARIABLE_LITERAL;
                    literalPair.second.push_back(literal);
                }
            }
            gfd._literalPairVector.push_back(literalPair);
        }
        return ifstream;
    }

    GFD(std::ifstream &ifstream);

    friend std::ostream &operator<<(std::ostream &out, GFD &gfd) {
        out << "GFD:pattern\n";
        out << gfd._pattern << "\n";
        out << "Literal:\n";
        for (auto &lit:gfd._literalPairVector) {
            out << "(";
            for (auto &lft: lit.first) {
                out << lft << " ";
            }
            out << ")->(";
            for (auto &rgt:lit.second) {
                out << rgt << " ";
            }
            out << ") ";
        }
        return out;
    }

    void init() {
        _literalPairVector.clear();
        LiteralPair literalPair;
        _literalPairVector.emplace_back(literalPair);
    }

    static bool literalCheck(Graph &queryGraph,
                             Graph &dataGraph,
                             std::map<int, int> &matchState,
                             LiteralPair &literalPairPointer) {
        for (auto &literal :literalPairPointer.first) {
            if (!literal.matchCheck(queryGraph,
                                    dataGraph,
                                    matchState))
                return false;
        }
        for (auto &literal :literalPairPointer.second) {
            if (!literal.matchCheck(queryGraph,
                                    dataGraph,
                                    matchState))
                return false;
        }
        return true;
    }

    void addLiteralDFS(std::map<std::pair<int, int>,
            std::set<PropertyValue>> &propertySet) {
        if (_literalPairVector.size() >= _maxLiteralSize)return;
        auto &newVertex = _pattern[_pattern.size() - 1];
        auto newVertexLabel = newVertex.getLabel();
        std::map<int, std::set<int>> labelPropertyNameMap{};

        std::vector<Literal> literalVector{};
//        添加x.A = c类型的literal
        for (auto &property:propertySet) {
            labelPropertyNameMap[property.first.first].insert(property.first.second);
            if (property.first.first != newVertexLabel)continue;

            auto propertyName = property.first.second;
            for (const auto &propertyValue: property.second) {
                ConstantLiteral tmpCon(static_cast<int>(_pattern.size() - 1),
                                       propertyName,
                                       propertyValue);
                Literal tmpLiteral;
                tmpLiteral._literalType = CONSTANT_LITERAL;
                tmpLiteral._constantLiteral = tmpCon;
                literalVector.push_back(tmpLiteral);
            }

        }

//        添加x.A=y.B的literal
//        枚举y
        for (int yIndex = 0; yIndex < _pattern.size() - 1; ++yIndex) {
            auto &vertex = _pattern.at(yIndex);
            if (vertex.getId() == _pattern.size() - 1)break;
//            枚举A
            for (auto &xPropertyName: labelPropertyNameMap[newVertexLabel]) {
//                枚举B
                for (auto &yPropertyName: labelPropertyNameMap[vertex.getLabel()]) {
                    VariableLiteral tmpVar(static_cast<int>(_pattern.size() - 1),
                                           xPropertyName,
                                           yIndex,
                                           yPropertyName);
                    Literal tmpLiteral;
                    tmpLiteral._literalType = VARIABLE_LITERAL;
                    tmpLiteral._variableLiteral = tmpVar;
                    literalVector.push_back(tmpLiteral);
                }
            }
        }

        addLiteralDFS(literalVector, 0);
    }

    static bool conflict(Literal &literal, std::vector<Literal> &literalVector) {
        for (auto &lit : literalVector) {
            if (literal._literalType != CONSTANT_LITERAL)continue;
            if (lit._literalType != CONSTANT_LITERAL)continue;
            if (lit._constantLiteral.conflict(literal._constantLiteral))return true;
        }
        return false;
    }

    void insertLiteral(Literal &literal, LiteralPair literalPair) {
        auto tmp = literalPair;
        if (literalPair.first.size() < _maxXLiteralVectorSize
            && !conflict(literal, literalPair.first)) {
            tmp.first.push_back(literal);
            if (_literalPairVector.size() <= _maxLiteralSize)
                _literalPairVector.push_back(tmp);
        }
        tmp = literalPair;

        if (literalPair.second.size() < _maxYLiteralVectorSize
            && !conflict(literal, literalPair.second)) {
            tmp.second.push_back(literal);
            if (_literalPairVector.size() <= _maxLiteralSize)
                _literalPairVector.push_back(tmp);
        }

    }

    void addLiteralDFS(std::vector<Literal> &literalVector,
                       int literalIndex) {
        if (_literalPairVector.size() >= _maxLiteralSize)return;
        if (literalIndex >= literalVector.size())
            return;
        auto literalPairVectorSize = _literalPairVector.size();
        auto &currentLiteral = literalVector.at(literalIndex);
        for (int i = 0; i < literalPairVectorSize; ++i) {
            insertLiteral(currentLiteral, _literalPairVector.at(i));
        }
        addLiteralDFS(literalVector, literalIndex + 1);
    }

    void fileOut(std::ofstream &fOut) {

        _pattern.fileOut(fOut);
        fOut << _literalPairVector.size() << std::endl;
        for (auto &literal:_literalPairVector) {
            auto X = literal.first;
            fOut << X.size() << std::endl;
            for (auto &lit: X) {
                if (lit._literalType == CONSTANT_LITERAL) {
                    lit._constantLiteral.fileOut(fOut);
                } else {
                    lit._variableLiteral.fileOut(fOut);
                }
            }
            auto Y = literal.second;
            fOut << Y.size() << std::endl;
            for (auto &lit: Y) {
                if (lit._literalType == CONSTANT_LITERAL) {
                    lit._constantLiteral.fileOut(fOut);
                } else {
                    lit._variableLiteral.fileOut(fOut);
                }
            }
        }
    }

};

GFD::GFD(std::ifstream &ifstream) {

    _pattern.clear();
    int vertexNumber;
    ifstream >> vertexNumber;
    for (int i = 0; i < vertexNumber; ++i) {
        int id, label;
        ifstream >> id >> label;
        Vertex tmpVertex(id, label);
        int propertyNumber;
        ifstream >> propertyNumber;
        for (int j = 0; j < propertyNumber; ++j) {
            int propertyName;
            auto *propertyValue = new int;
            ifstream >> propertyName >> (*propertyValue);
            PropertyValue _propertyValue;
            _propertyValue._propertyValueType = INT;
            _propertyValue._propertyValuePointer = propertyValue;
            tmpVertex.PropertySet.insert({propertyName, _propertyValue});
        }
        _pattern.push_back(tmpVertex);

    }

    int edgeNumber;
    ifstream >> edgeNumber;
    for (int i = 0; i < edgeNumber; ++i) {
        int from, to, label;
        ifstream >> from >> to >> label;
        Graph::makeEdge(_pattern.at(from), _pattern.at(to), label);
    }

    int literalSize;
    ifstream >> literalSize;
    for (int i = 0; i < literalSize; ++i) {
        LiteralPair literalPair;
        int xSize;
        ifstream >> xSize;
        for (int j = 0; j < xSize; ++j) {
            std::string literalType;
            ifstream >> literalType;
            if (literalType.at(0) == 'c') {
                int ind, propertyName;
                auto valuePointer = new int;
                ifstream >> ind >> propertyName >> (*valuePointer);

                PropertyValue propertyValue;
                propertyValue._propertyValueType = INT;
                propertyValue._propertyValuePointer = valuePointer;
                ConstantLiteral constantLiteral(ind, propertyName, propertyValue);

                Literal literal;
                literal._constantLiteral = constantLiteral;
                literal._literalType = CONSTANT_LITERAL;
                literalPair.first.push_back(literal);
            } else {
                int xInd, yInd, xPropertyName, yPropertyName;
                ifstream >> xInd >> xPropertyName >> yInd >> yPropertyName;
                VariableLiteral variableLiteral(xInd, xPropertyName, yInd, yPropertyName);

                Literal literal;
                literal._variableLiteral = variableLiteral;
                literal._literalType = VARIABLE_LITERAL;
                literalPair.first.push_back(literal);
            }
        }
        int ySize;
        ifstream >> ySize;
        for (int j = 0; j < ySize; ++j) {
            std::string literalType;
            ifstream >> literalType;
            if (literalType.at(0) == 'c') {
                int ind, propertyName;
                auto valuePointer = new int;
                ifstream >> ind >> propertyName >> (*valuePointer);

                PropertyValue propertyValue;
                propertyValue._propertyValueType = INT;
                propertyValue._propertyValuePointer = valuePointer;
                ConstantLiteral constantLiteral(ind, propertyName, propertyValue);

                Literal literal;
                literal._constantLiteral = constantLiteral;
                literal._literalType = CONSTANT_LITERAL;
                literalPair.second.push_back(literal);
            } else {
                int xInd, yInd, xPropertyName, yPropertyName;
                ifstream >> xInd >> xPropertyName >> yInd >> yPropertyName;
                VariableLiteral variableLiteral(xInd, xPropertyName, yInd, yPropertyName);

                Literal literal;
                literal._variableLiteral = variableLiteral;
                literal._literalType = VARIABLE_LITERAL;
                literalPair.second.push_back(literal);
            }
        }
        _literalPairVector.push_back(literalPair);
    }
}

int GFD::_maxXLiteralVectorSize = 2;
int GFD::_maxYLiteralVectorSize = 1;
int GFD::_maxEdgeSize = 3;
int GFD::_maxVertexSize = 2;
int GFD::_maxLiteralSize = 3;


#endif //GFD_GFD_H
